
package com.kanjian.music.volley;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.support.v4.util.LruCache;

import com.android.volley.toolbox.ImageLoader;
import com.kanjian.music.KanjianApplication;
import com.kanjian.music.util.SDCardUtil;
import com.kanjian.music.util.VersionUtil;

/**
 * 位图缓存工具 memorycache + diskcache
 * 
 * @author xuyanjun@kanjian.com
 */
public class BitmapLruCache implements ImageLoader.ImageCache {

    private LruCache<String, Bitmap> mLruCache;

    private DiskLruCache mDiskLruCache;

    private final Object mDiskCacheLock = new Object();

    // private boolean mDiskCacheStarting = true;

    private static final int DISK_CACHE_INDEX = 0;

    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 100; // 100MB

    public BitmapLruCache(int maxSize) {
        mLruCache = new LruCache<String, Bitmap>(maxSize);
        new InitDiskCacheTask().execute();
    }

    @Override
    public Bitmap getBitmap(String key) {
        Bitmap data = getBitmapFromMemCache(key);
        if (data == null) {
            data = getBitmapFromDiskCache(key);
        }
        return data;
    }

    @Override
    public void putBitmap(String key, Bitmap bitmap) {
        addBitmapToCache(key, bitmap);
    }

    private Bitmap getBitmapFromMemCache(String key) {
        return mLruCache.get(key);
    }

    private Bitmap getBitmapFromDiskCache(String key) {
        if (mDiskLruCache != null) {
            InputStream inputStream = null;
            try {
                final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(hashKeyForDisk(key));
                if (snapshot != null) {
                    inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
                    if (inputStream != null) {
                        final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                        if (bitmap != null) {
                            return bitmap;
                        }
                    }
                }
            } catch (final IOException e) {
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (final IOException e) {
                }
            }
        }
        return null;
    }

    private synchronized void addBitmapToCache(String data, Bitmap bitmap) {
        if (data == null || bitmap == null) {
            return;
        }

        // Add to memory cache
        if (mLruCache != null && mLruCache.get(data) == null) {
            mLruCache.put(data, bitmap);
        }

        // Add to disk cache
        if (mDiskLruCache != null) {
            final String key = hashKeyForDisk(data);
            try {
                if (mDiskLruCache.get(key) == null) {
                    final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        final OutputStream out = editor.newOutputStream(DISK_CACHE_INDEX);
                        bitmap.compress(CompressFormat.JPEG, 100, out);
                        editor.commit();
                    }
                }
            } catch (final IOException e) {
            }
        }
    }

    /**
     * A hashing method that changes a string (like a URL) into a hash suitable
     * for using as a disk filename.
     */
    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }

        return cacheKey;
    }

    private static String bytesToHexString(byte[] bytes) {
        // http://stackoverflow.com/questions/332079
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
        @Override
        protected Void doInBackground(File... params) {
            synchronized (mDiskCacheLock) {
                File cacheDir = getDiskCacheDir(KanjianApplication.getContext());
                try {
                    if (getUsableSpace(cacheDir) > DISK_CACHE_SIZE) {
                        mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_CACHE_SIZE);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    mDiskCacheLock.notifyAll(); // Wake any waiting threads
                }
            }
            return null;
        }
    }

    public static File getDiskCacheDir(Context context) {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            return SDCardUtil.getExternalCacheDir(context);
        } else {
            return SDCardUtil.getCacheDir(context);
        }
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    public static long getUsableSpace(File cacheDir) {
        if (VersionUtil.hasGingerbread()) {
            return cacheDir.getUsableSpace();
        }
        final StatFs stats = new StatFs(cacheDir.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }

}
